home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / lib / rhythmbox / plugins / lyrics / __init__.py next >
Encoding:
Python Source  |  2009-04-07  |  10.9 KB  |  388 lines

  1. # -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
  2. #
  3. # Copyright (C) 2006 Jonathan Matthew
  4. # Copyright (C) 2007 James Livingston
  5. # Copyright (C) 2007 Sirio Bola√±os Puchet
  6. #
  7. # This program is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation; either version 2, or (at your option)
  10. # any later version.
  11. #
  12. # The Rhythmbox authors hereby grant permission for non-GPL compatible
  13. # GStreamer plugins to be used and distributed together with GStreamer
  14. # and Rhythmbox. This permission is above and beyond the permissions granted
  15. # by the GPL license by which Rhythmbox is covered. If you modify this code
  16. # you may extend this exception to your version of the code, but you are not
  17. # obligated to do so. If you do not wish to do so, delete this exception
  18. # statement from your version.
  19. #
  20. # This program is distributed in the hope that it will be useful,
  21. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  22. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  23. # GNU General Public License for more details.
  24. #
  25. # You should have received a copy of the GNU General Public License
  26. # along with this program; if not, write to the Free Software
  27. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
  28. #
  29. # TODO:
  30. # - check that the lyrics returned even remotely match the request?
  31.  
  32. import os, re
  33. import gtk, gtk.glade
  34. import gconf
  35. import rhythmdb, rb
  36.  
  37. import LyricsParse
  38. from LyricsConfigureDialog import LyricsConfigureDialog
  39.  
  40. ui_str = """
  41. <ui>
  42.   <menubar name="MenuBar">
  43.     <menu name="ViewMenu" action="View">
  44.       <menuitem name="ViewSongLyrics" action="ViewSongLyrics"/>
  45.     </menu>
  46.   </menubar>
  47. </ui>
  48. """
  49.  
  50. LYRIC_TITLE_STRIP=["\(live[^\)]*\)", "\(acoustic[^\)]*\)", "\([^\)]*mix\)", "\([^\)]*version\)", "\([^\)]*edit\)", "\(feat[^\)]*\)"]
  51. LYRIC_TITLE_REPLACE=[("/", "-"), (" & ", " and ")]
  52. LYRIC_ARTIST_REPLACE=[("/", "-"), (" & ", " and ")]
  53.  
  54. gconf_keys = {    'engines' : '/apps/rhythmbox/plugins/lyrics/engines',
  55.         'folder': '/apps/rhythmbox/plugins/lyrics/folder'
  56.          }
  57.  
  58.  
  59. def create_lyrics_view():
  60.     tview = gtk.TextView()
  61.     tview.set_wrap_mode(gtk.WRAP_WORD)
  62.     tview.set_editable(False)
  63.     tview.set_left_margin(6)
  64.  
  65.     tview.set_size_request (0, 0)
  66.     sw = gtk.ScrolledWindow()
  67.     sw.add(tview)
  68.     sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
  69.     sw.set_shadow_type(gtk.SHADOW_IN)
  70.  
  71.     vbox = gtk.VBox(spacing=12)
  72.     vbox.pack_start(sw, expand=True)
  73.     
  74.     return (vbox, tview.get_buffer(), tview)
  75.  
  76. def parse_song_data(artist, title):
  77.     
  78.     # replace ampersands and the like
  79.     for exp in LYRIC_ARTIST_REPLACE:
  80.         artist = re.sub(exp[0], exp[1], artist)
  81.     for exp in LYRIC_TITLE_REPLACE:
  82.         title = re.sub(exp[0], exp[1], title)
  83.  
  84.     # strip things like "(live at Somewhere)", "(accoustic)", etc
  85.     for exp in LYRIC_TITLE_STRIP:
  86.         title = re.sub (exp, '', title)
  87.  
  88.     # compress spaces
  89.     title = title.strip()
  90.     artist = artist.strip()    
  91.     
  92.     return (artist, title)
  93.     
  94. def build_cache_path(artist, title):
  95.     folder = gconf.client_get_default().get_string(gconf_keys['folder'])
  96.     if folder is None:
  97.         folder = os.path.join(rb.user_cache_dir(), "lyrics")
  98.  
  99.     lyrics_folder = os.path.expanduser (folder)
  100.     if not os.path.exists (lyrics_folder):
  101.         os.mkdir (lyrics_folder)
  102.  
  103.     artist_folder = os.path.join(lyrics_folder, artist[:128])
  104.     if not os.path.exists (artist_folder):
  105.         os.mkdir (artist_folder)
  106.  
  107.     return os.path.join(artist_folder, title[:128] + '.lyric')
  108.  
  109. class LyricGrabber(object):
  110.     def __init__(self, db, entry):
  111.         self.db = db
  112.         self.entry = entry
  113.         
  114.         self.artist = self.db.entry_get(self.entry, rhythmdb.PROP_ARTIST).lower()
  115.         self.title = self.db.entry_get(self.entry, rhythmdb.PROP_TITLE).lower()
  116.  
  117.         (self.artist, self.title) = parse_song_data(self.artist, self.title)
  118.  
  119.         self.cache_path = build_cache_path(self.artist, self.title)
  120.  
  121.     def verify_lyric(self):
  122.         return os.path.exists(self.cache_path)
  123.       
  124.     def search_lyrics(self, callback, cache_only=False):
  125.         self.callback = callback
  126.         
  127.         status = self.verify_lyric()
  128.         
  129.         if status:
  130.             l = rb.Loader()
  131.             l.get_url(self.cache_path, callback)
  132.         else:
  133.             if cache_only:
  134.                 self.callback(_("No lyrics found"))
  135.             else:
  136.                 def lyric_callback (text):
  137.                     if text is not None:
  138.                         f = file (self.cache_path, 'w')
  139.                         f.write (text)
  140.                         f.close ()
  141.                         self.callback(text)
  142.                     else:
  143.                         self.callback(_("No lyrics found"))
  144.  
  145.                 parser = LyricsParse.Parser(gconf_keys, self.artist, self.title)
  146.                 parser.get_lyrics(lyric_callback)
  147.  
  148. class LyricPane(object):
  149.     def __init__(self, db, song_info):
  150.         self.db = db
  151.         self.song_info = song_info
  152.         self.entry = self.song_info.get_property("current-entry")
  153.         
  154.         self.build_path()
  155.         
  156.         def save_lyrics(cache_path, text):
  157.             f = file (cache_path, 'w')
  158.             f.write (text)
  159.             f.close ()
  160.         
  161.         def erase_lyrics(cache_path):
  162.             f = file (cache_path, 'w')
  163.             f.write ("")
  164.             f.close ()
  165.         
  166.         def save_callback():
  167.             buf = self.buffer
  168.             startiter = buf.get_start_iter()
  169.             enditer = buf.get_end_iter()
  170.             text = buf.get_text(startiter, enditer)
  171.             save_lyrics(self.cache_path, text)
  172.             self.get_lyrics()
  173.         
  174.         def edit_callback(widget):
  175.             if self.edit.get_active() == 1:
  176.                 self.tview.set_editable(True)
  177.                 self.edit.set_label(_("_Save"))
  178.             else:
  179.                 if self.cache_path is not None:
  180.                     save_callback()
  181.                 self.tview.set_editable(False)
  182.                 self.edit.set_label(_("_Edit"))
  183.  
  184.         def discard_callback(widget):
  185.             if self.cache_path is not None and os.path.exists(self.cache_path):
  186.                 os.remove(self.cache_path)
  187.             self.get_lyrics()
  188.         
  189.         def clear_callback(widget):
  190.             if self.cache_path is not None and os.path.exists (self.cache_path):
  191.                 erase_lyrics(self.cache_path)
  192.             self.get_lyrics()
  193.        
  194.  
  195.         self.edit = gtk.ToggleButton(_("_Edit"))
  196.         self.edit.connect('toggled', edit_callback)
  197.         self.discard = gtk.Button(_("_Search again"))
  198.         self.discard.connect('clicked', discard_callback)
  199.         self.clear = gtk.Button(stock=gtk.STOCK_CLEAR)
  200.         self.clear.connect('clicked', clear_callback)
  201.         self.hbox = gtk.HButtonBox()
  202.         self.hbox.set_spacing (6)
  203.         self.hbox.set_layout(gtk.BUTTONBOX_END)
  204.         self.hbox.add(self.edit)
  205.         self.hbox.add(self.clear)
  206.         self.hbox.add(self.discard)
  207.         self.hbox.set_child_secondary (self.clear, is_secondary=True)
  208.  
  209.         (self.view, self.buffer, self.tview) = create_lyrics_view()
  210.  
  211.         self.view.pack_start(self.hbox, expand=False, fill=False, padding=6)
  212.         self.view.set_spacing(2)
  213.     
  214.         self.view.show_all()
  215.         self.page_num = song_info.append_page(_("Lyrics"), self.view)
  216.         self.have_lyrics = 0
  217.         self.visible = 0
  218.  
  219.         self.entry_change_id = song_info.connect('notify::current-entry', self.entry_changed)
  220.         nb = self.view.get_parent()
  221.         self.switch_page_id = nb.connect('switch-page', self.switch_page_cb)
  222.         
  223.         #self.get_lyrics()
  224.  
  225.     def build_path(self):
  226.  
  227.         artist = self.db.entry_get(self.entry, rhythmdb.PROP_ARTIST).lower()
  228.         title = self.db.entry_get(self.entry, rhythmdb.PROP_TITLE).lower()
  229.         (artist, title) = parse_song_data(artist, title)
  230.         cache_path = build_cache_path(artist, title)
  231.         self.cache_path = cache_path
  232.  
  233.     def entry_changed(self, pspec, duh):
  234.         self.entry = self.song_info.get_property("current-entry")
  235.         self.have_lyrics = 0
  236.         if self.visible != 0:
  237.             self.build_path()
  238.             self.get_lyrics()
  239.  
  240.     def switch_page_cb(self, notebook, page, page_num):
  241.         if self.have_lyrics != 0:
  242.             return
  243.  
  244.         if page_num != self.page_num:
  245.             self.visible = 0
  246.             return
  247.  
  248.         self.visible = 1
  249.         self.get_lyrics()
  250.  
  251.     def get_lyrics(self):
  252.         if self.entry is None:
  253.             return
  254.  
  255.         self.buffer.set_text(_("Searching for lyrics..."));
  256.         lyrics_grabber = LyricGrabber(self.db, self.entry)
  257.         lyrics_grabber.search_lyrics(self.buffer.set_text)
  258.             
  259.  
  260. class LyricWindow (gtk.Window):
  261.  
  262.     def __init__(self):
  263.         gtk.Window.__init__(self)
  264.         self.set_border_width(12)
  265.  
  266.         close = gtk.Button(stock=gtk.STOCK_CLOSE)
  267.         close.connect('clicked', lambda w: self.destroy())
  268.     
  269.         (lyrics_view, buffer, tview) = create_lyrics_view()
  270.         self.buffer = buffer
  271.         bbox = gtk.HButtonBox()
  272.         bbox.set_layout(gtk.BUTTONBOX_END)
  273.         bbox.pack_start(close)
  274.         lyrics_view.pack_start(bbox, expand=False)
  275.     
  276.         self.add(lyrics_view)
  277.         self.set_default_size(400, 300)
  278.         self.show_all()
  279.  
  280.     def s_title(self, title, artist):
  281.         self.set_title(title + " - " + artist + " - " + _("Lyrics"))
  282.  
  283. class LyricsDisplayPlugin(rb.Plugin):
  284.  
  285.     def __init__ (self):
  286.         rb.Plugin.__init__ (self)
  287.         self.window = None
  288.  
  289.     def activate (self, shell):
  290.         self.shell = shell
  291.         self.action = gtk.Action ('ViewSongLyrics', _('Song L_yrics'),
  292.                       _('Display lyrics for the playing song'),
  293.                       'rb-song-lyrics')
  294.         self.activate_id = self.action.connect ('activate', self.show_song_lyrics, shell)
  295.         
  296.         self.action_group = gtk.ActionGroup ('SongLyricsPluginActions')
  297.         self.action_group.add_action_with_accel (self.action, "<control>L")
  298.         
  299.         uim = shell.get_ui_manager ()
  300.         uim.insert_action_group (self.action_group, 0)
  301.         self.ui_id = uim.add_ui_from_string (ui_str)
  302.         uim.ensure_update ()
  303.  
  304.         sp = shell.get_player ()
  305.         self.pec_id = sp.connect('playing-song-changed', self.playing_entry_changed)
  306.         self.current_entry = None
  307.         self.playing_entry_changed (sp, sp.get_playing_entry ())
  308.  
  309.         self.csi_id = shell.connect('create_song_info', self.create_song_info)
  310.  
  311.     def deactivate (self, shell):
  312.             
  313.         uim = shell.get_ui_manager()
  314.         uim.remove_ui (self.ui_id)
  315.         uim.remove_action_group (self.action_group)
  316.  
  317.         self.action_group = None
  318.         self.action = None
  319.  
  320.         sp = shell.get_player ()
  321.         sp.disconnect (self.pec_id)
  322.         shell.disconnect (self.csi_id)
  323.  
  324.         if self.window is not None:
  325.             self.window.destroy ()
  326.             self.window = None
  327.  
  328.     def create_configure_dialog(self, dialog=None):
  329.         if not dialog:
  330.             glade_file = self.find_file("lyrics-prefs.glade")
  331.             dialog = LyricsConfigureDialog (glade_file, gconf_keys).get_dialog()
  332.         dialog.present()
  333.         return dialog
  334.     
  335.     def playing_entry_changed (self, sp, entry):
  336.         if entry is not None:
  337.             self.action.set_sensitive (True)
  338.             self.update_song_lyrics(entry)
  339.         else:
  340.             self.action.set_sensitive (False)
  341.  
  342.     def update_song_lyrics(self, entry):
  343.         if entry == self.current_entry:
  344.             return
  345.         
  346.         db = self.shell.get_property ("db")
  347.         
  348.         if self.window is None:
  349.             return
  350.  
  351.         title = db.entry_get(entry, rhythmdb.PROP_TITLE)
  352.         artist = db.entry_get(entry, rhythmdb.PROP_ARTIST)
  353.  
  354.         self.window.s_title(title, artist)
  355.         lyrics_grabber = LyricGrabber(db, entry)
  356.         lyrics_grabber.search_lyrics(self.window.buffer.set_text)
  357.  
  358.     def show_song_lyrics (self, action, shell):
  359.  
  360.         if self.window is not None:
  361.             self.window.destroy ()
  362.             self.window = None
  363.  
  364.         db = shell.get_property ("db")
  365.         sp = shell.get_player ()
  366.         entry = sp.get_playing_entry ()
  367.  
  368.         if entry is None:
  369.             return
  370.         
  371.         title = db.entry_get(entry, rhythmdb.PROP_TITLE)
  372.         artist = db.entry_get(entry, rhythmdb.PROP_ARTIST)
  373.  
  374.         self.window = LyricWindow()
  375.         self.window.s_title(title, artist)
  376.         self.window.connect("destroy", self.window_deleted)
  377.         lyrics_grabber = LyricGrabber(db, entry)
  378.         lyrics_grabber.search_lyrics(self.window.buffer.set_text)
  379.  
  380.     def window_deleted (self, window):
  381.         print "lyrics window destroyed"
  382.         self.window = None
  383.     
  384.     def create_song_info (self, shell, song_info, is_multiple):
  385.  
  386.         if is_multiple is False:
  387.             x = LyricPane(shell.get_property ("db"), song_info)
  388.